/**
 * 
 */
package dk.dtu.is.controllers;


import java.io.StringReader;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import dk.dtu.is.model.iec61850.MeasuredValue;
import dk.dtu.is.model.iec61850.ln.Zcev;

/**
 * @author dsac
 *
 */
public class NodeParser {
	
	private String node;
	
	/**
	 * Constructor
	 */
	public NodeParser(String node) {
		this.node = node;
	}
	
	/**
	 * It checks whether the current node contains the @logicalNodeName
	 * @param logicalNodeName
	 * @return
	 */
	public boolean hasThisLogicalNode(String logicalNodeName) {
		// TODO
		return true;
	}
	
	/**
	 * Parse the node and get the ZCEV object back.
	 * 
	 * @return
	 */
	public Zcev getZcev() {
		Zcev zcev = null;
		
		if (node == null) {
			return null;
		}
		
		if (!(hasThisLogicalNode("ZCEV"))) {
			return null;
		}
		
		// Parse the data, filtering out the start elements
        XMLInputFactory xmlif = XMLInputFactory.newInstance();
        StringReader sr = new StringReader(node);
        XMLStreamReader reader = null;
		
        try {
        	reader = xmlif.createXMLStreamReader(sr);
        	readRoot(reader); // skip first useless elements
			zcev = readZcevLogicalNode(reader);
			// System.out.println("ZCEV parsed: " + zcev.toString());
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return zcev;
	}
	
	
	/**
	 * Simple method to move the cursor to the beginning of the LogicalNode
	 *  
	 * @param reader
	 * @throws XMLStreamException
	 */
	private void readRoot(XMLStreamReader reader) throws XMLStreamException {
		// skip all the possible initial elements
		reader.nextTag();
	}
	
	/**
	 * Read abd return a ZCEV as defined in the extension of the IEC 61850 standard.
	 * 
	 * @param reader
	 * @return
	 * @throws XMLStreamException
	 */
	private Zcev readZcevLogicalNode(XMLStreamReader reader) throws XMLStreamException
	{
		Zcev zcev = new Zcev();
		
		QName name;
		String type;
		
		// Test that the first part is a Logical Node of type ZCEV
	    name = reader.getName( );
	    boolean isLogicalNode = "LN".equals( name.getLocalPart( ) );
	    if (!(isLogicalNode))
	       	throw new XMLStreamException("readZcevLogicalNode is supposed to read a LogicalNode. Found: " + name);
	        
        //check if it is a MeasurementValue
        type = reader.getAttributeValue(null, "Type");
        boolean isZcev = "ZCEV".equals(type);
        if (!(isZcev))
        	throw new XMLStreamException("readZcevLogicalNode is supposed to read a ZCEV. Found: " + type);
        	    
	    // read the first DataObject on the ZCEV
	    // this comes immediately after the LogicalNode element
		reader.nextTag();
		
		// I keep iterating the logical node until I get to the end of the logical node element.
		// this is defined as a END_ELEMENT whose name is "LN".
		// NB: a logical node can not contain any other logical node
	    while ( ! (reader.getName().getLocalPart().equals("LN")))
	    {    	
	    	// Test that we have a data attribute inside
	        name = reader.getName( );
	        boolean isDataObject = "DO".equals( name.getLocalPart( ) );
	        if (!(isDataObject))
	        	throw new XMLStreamException("readZcevLogicalNode is supposed to contain only DataObject (DO). Found: " + name);

	        // start the real parsing
			type = reader.getAttributeValue(null, "Type");
			String nameAttribute = reader.getAttributeValue(null, "Name");
			
			switch (type) {
			case "MV":
				MeasuredValue mv;
				switch (nameAttribute) {
				case "SOC":
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setSoc(mv.getAnalogueValue());
					break;
				case "Vol":
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setVol(mv.getAnalogueValue());
					break;
				case "Amp":
					mv = this.readMeasurementValueDataObject(reader);
					zcev.setCur(mv.getAnalogueValue());
					break;
// TODO: uncomment this part when the proper parsing method for the cell voltages will have been implemented 				
//				case "CellVols":
//					mv = this.readMeasurementValueDataObject(reader);
//					zcev.setCellVols(mv.getAnalogueValues());
//					break;
				default:
					skipDataObject(reader);
					break;
				}
				break;
//				case "ASG":
//					...
//				    break
//				...
			default:
				// if what I am reading is not in the case list above, I skip all the internal children (which are DataObject)
				skipDataObject(reader);	
				break;
			} // case
	    } // while
	
		return zcev;  
	}
	
	/**
	 * It reads and returns a MeasurementValue as defined in IEC 61850 standard
	 * @param reader
	 * @return
	 * @throws XMLStreamException
	 */
	private MeasuredValue readMeasurementValueDataObject(XMLStreamReader reader) throws XMLStreamException
	{
		MeasuredValue mv = new MeasuredValue();
		
		QName name;
		String type;

    	// check if it is a DataObject
        name = reader.getName( );
        boolean isDataObject = "DO".equals( name.getLocalPart( ) );
        if (!(isDataObject))
        	throw new XMLStreamException("readMeasurementValueDataObject is supposed to read a DataObject. Found: "+name);
        
        //check if it is a MeasurementValue
        type = reader.getAttributeValue(null, "Type");
        boolean isMeasurementValue = "MV".equals(type);
        if (!(isMeasurementValue))
        	throw new XMLStreamException("readMeasurementValueDataObject is supposed to read a DataObject whose Type is \"MV\". Found: " + type);
	    
	    // go one lever lower: start processing its DataAttributes	        
	    while ( reader.nextTag( ) == XMLEvent.START_ELEMENT )
	    {
	    	// Test that we have a data attribute inside
	        name = reader.getName( );
	        boolean isDataAttribute = "DA".equals( name.getLocalPart( ) );
	        if (!(isDataAttribute))
	        	throw new XMLStreamException("readMeasurementValueDataObject is supposed to contain only DataAttributes. Found: "+name);
	        
			type = reader.getAttributeValue(null, "Type");
			switch (type) {
			case "AnalogueValue":
			    // access the START ELEMENT which contains the value, that is, Type=FLOAT32
				reader.nextTag( );
				if (reader.getAttributeValue(null, "IsArray") != null) {
					mv.setAnalogueValues(this.getAnalogueValues(reader));
					mv.setArray(true);
				} else {
					mv.setAnalogueValue(this.getAnalogueValue(reader));
				}
				break;
//				case "Quality":
//					<mv.setQuality>
//				    break
//				...
			default:
				skipDataAttributes(reader);
				break;
			} // case
	        
	    } // while
	    
	    // skip the END_ELEMENT of the current DataObject which contained the Measurement Value
	    reader.next();
	
		return mv;  
	}
	

	/**
	 * Common Data Attribute Types: Analogue Value
	 * 61850-7-3, Chapter 6.3, 
	 * 
	 * Analogue values may be represented as a basic data type INTEGER (attribute i) or as FLOATING POINT (attribute f). 
	 * At least one of the attributes shall be used.
	 * 
	 * As an example, if xxx.f is written, the application shall update xxx.i accordingly.
	 * 
	 */
	private float getAnalogueValue(XMLStreamReader reader)  throws XMLStreamException
	{
		Float value = null;
		
		// check if it is a DA
		QName name = reader.getName( );
	    boolean isDataAttribute = "DA".equals( name.getLocalPart( ) );
	    if (! (isDataAttribute))
	     	throw new XMLStreamException("getAnalogueValue is supposed to read only DataAttributes. Found: "+name);
	    
	    // extract the integer or float value
		boolean isFloat = "f".equals(reader.getAttributeValue(null, "Name"));
		boolean isInteger = "i".equals(reader.getAttributeValue(null, "Name"));
		if (isFloat || isInteger) {
			String elementText = reader.getElementText();
		    value = new Float(Float.parseFloat(elementText));
        }
				
		// skip the END ELEMENT Tag
		reader.next();
		
	    return value.floatValue();
	}
	
	
	/**
	 * Gets the analogue values.
	 *
	 * @param reader the reader
	 * @return the analogue values
	 */
	private float[] getAnalogueValues(XMLStreamReader reader) {
		// TODO follow the same structure of the "getAnalogueValue" method and extract the single cell voltages.
		
		return null;
	}
	/**
	 * It skips an entire dataObject when the cursor is on it.
	 * It moves the cursor forward to the next DataObject.
	 * 
	 * @param reader 
	 * @throws XMLStreamException
	 */
	private void skipDataObject(XMLStreamReader reader) throws XMLStreamException
	{
		// System.out.println("Skipping DO, Type: \"" + reader.getAttributeValue("", "Type") + "\", Name: \"" + reader.getAttributeValue("", "Name") + "\"");
		
		// skip all the internal children (which are DataObject)
		
		boolean dataObjectEndElementFound = false;
		while (!dataObjectEndElementFound)
		{
			int eventType = reader.next( );
			
			// continue to read the next event until I get to a START ELEMENT
			if (eventType == XMLEvent.START_ELEMENT) {
				//System.out.println("  Skipping the DA: \"" + reader.getAttributeValue("", "Name") + "\"");
			}
			
			if (eventType == XMLEvent.END_ELEMENT)
				if (reader.getName().getLocalPart().equals("DO")) {
					dataObjectEndElementFound = true;
					//System.out.println("End DO\n");
				}
		}
		// move to the next DataObject
		reader.nextTag();
	}

	/**
	 * It skips an entire dataAttibute when the cursor is on it.
	 * It moves the cursor forward to the next DataAttribute.
	 * 
	 * If this DataAttributes has children (other DataAttributes), it also skips them
	 * 
	 * @param reader 
	 * @param currentOpenElement
	 * @throws XMLStreamException
	 */
	private void skipDataAttributes(XMLStreamReader reader) throws XMLStreamException {
		int openElementsCount = 1;
		int eventType;
		while (openElementsCount != 0)
		{
			eventType = reader.next();
			
			if (eventType == XMLEvent.START_ELEMENT)
				openElementsCount++;
			if (eventType == XMLEvent.END_ELEMENT)
				openElementsCount--;
		}
	}

	/**
	 * Utility class.
	 * It returns the string name of the Event Type
	 * @param eventType
	 * @return
	 */
	public static final String getEventTypeString(int eventType) {
		switch (eventType) {
			case XMLEvent.START_ELEMENT:
			return "START_ELEMENT";
	
			case XMLEvent.END_ELEMENT:
			return "END_ELEMENT";
	
			case XMLEvent.PROCESSING_INSTRUCTION:
			return "PROCESSING_INSTRUCTION";
	
			case XMLEvent.CHARACTERS:
			return "CHARACTERS";
	
			case XMLEvent.COMMENT:
			return "COMMENT";
	
			case XMLEvent.START_DOCUMENT:
			return "START_DOCUMENT";
	
			case XMLEvent.END_DOCUMENT:
			return "END_DOCUMENT";
	
			case XMLEvent.ENTITY_REFERENCE:
			return "ENTITY_REFERENCE";
	
			case XMLEvent.ATTRIBUTE:
			return "ATTRIBUTE";
	
			case XMLEvent.DTD:
			return "DTD";
	
			case XMLEvent.CDATA:
			return "CDATA";
	
			case XMLEvent.SPACE:
			return "SPACE";
		}
		
		return "";
	}

}
